/*
 * Copyright (c) 2019 Hemanth Savarala.
 *
 * Licensed under the GNU General Public License v3
 *
 * This is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by
 *  the Free Software Foundation either version 3 of the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 */

package code.name.monkey.retromusic.service;

import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.audiofx.AudioEffect;
import android.net.Uri;
import android.os.PowerManager;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.service.playback.Playback;
import code.name.monkey.retromusic.util.PreferenceUtil;

/** @author Andrew Neal, Karim Abou Zeid (kabouzeid) */
public class MultiPlayer
    implements Playback, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener {
  public static final String TAG = MultiPlayer.class.getSimpleName();

  private MediaPlayer mCurrentMediaPlayer = new MediaPlayer();
  private MediaPlayer mNextMediaPlayer;

  private Context context;
  @Nullable private Playback.PlaybackCallbacks callbacks;

  private boolean mIsInitialized = false;

  /** Constructor of <code>MultiPlayer</code> */
  MultiPlayer(final Context context) {
    this.context = context;
    mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
  }

  /**
   * @param path The path of the file, or the http/rtsp URL of the stream you want to play
   * @return True if the <code>player</code> has been prepared and is ready to play, false otherwise
   */
  @Override
  public boolean setDataSource(@NonNull final String path) {
    mIsInitialized = false;
    mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path);
    if (mIsInitialized) {
      setNextDataSource(null);
    }
    return mIsInitialized;
  }

  /**
   * @param player The {@link MediaPlayer} to use
   * @param path The path of the file, or the http/rtsp URL of the stream you want to play
   * @return True if the <code>player</code> has been prepared and is ready to play, false otherwise
   */
  private boolean setDataSourceImpl(@NonNull final MediaPlayer player, @NonNull final String path) {
    if (context == null) {
      return false;
    }
    try {
      player.reset();
      player.setOnPreparedListener(null);
      if (path.startsWith("content://")) {
        player.setDataSource(context, Uri.parse(path));
      } else {
        player.setDataSource(path);
      }
      player.setAudioStreamType(AudioManager.STREAM_MUSIC);
      player.prepare();
    } catch (Exception e) {
      return false;
    }
    player.setOnCompletionListener(this);
    player.setOnErrorListener(this);
    final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
    intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
    intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName());
    intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC);
    context.sendBroadcast(intent);
    return true;
  }

  /**
   * Set the MediaPlayer to start when this MediaPlayer finishes playback.
   *
   * @param path The path of the file, or the http/rtsp URL of the stream you want to play
   */
  @Override
  public void setNextDataSource(@Nullable final String path) {
    if (context == null) {
      return;
    }
    try {
      mCurrentMediaPlayer.setNextMediaPlayer(null);
    } catch (IllegalArgumentException e) {
      Log.i(TAG, "Next media player is current one, continuing");
    } catch (IllegalStateException e) {
      Log.e(TAG, "Media player not initialized!");
      return;
    }
    if (mNextMediaPlayer != null) {
      mNextMediaPlayer.release();
      mNextMediaPlayer = null;
    }
    if (path == null) {
      return;
    }
    if (PreferenceUtil.INSTANCE.isGapLessPlayback()) {
      mNextMediaPlayer = new MediaPlayer();
      mNextMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
      mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
      if (setDataSourceImpl(mNextMediaPlayer, path)) {
        try {
          mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
        } catch (@NonNull IllegalArgumentException | IllegalStateException e) {
          Log.e(TAG, "setNextDataSource: setNextMediaPlayer()", e);
          if (mNextMediaPlayer != null) {
            mNextMediaPlayer.release();
            mNextMediaPlayer = null;
          }
        }
      } else {
        if (mNextMediaPlayer != null) {
          mNextMediaPlayer.release();
          mNextMediaPlayer = null;
        }
      }
    }
  }

  /**
   * Sets the callbacks
   *
   * @param callbacks The callbacks to use
   */
  @Override
  public void setCallbacks(@Nullable final Playback.PlaybackCallbacks callbacks) {
    this.callbacks = callbacks;
  }

  /** @return True if the player is ready to go, false otherwise */
  @Override
  public boolean isInitialized() {
    return mIsInitialized;
  }

  /** Starts or resumes playback. */
  @Override
  public boolean start() {
    try {
      mCurrentMediaPlayer.start();
      return true;
    } catch (IllegalStateException e) {
      return false;
    }
  }

  /** Resets the MediaPlayer to its uninitialized state. */
  @Override
  public void stop() {
    mCurrentMediaPlayer.reset();
    mIsInitialized = false;
  }

  /** Releases resources associated with this MediaPlayer object. */
  @Override
  public void release() {
    stop();
    mCurrentMediaPlayer.release();
    if (mNextMediaPlayer != null) {
      mNextMediaPlayer.release();
    }
  }

  /** Pauses playback. Call start() to resume. */
  @Override
  public boolean pause() {
    try {
      mCurrentMediaPlayer.pause();
      return true;
    } catch (IllegalStateException e) {
      return false;
    }
  }

  /** Checks whether the MultiPlayer is playing. */
  @Override
  public boolean isPlaying() {
    return mIsInitialized && mCurrentMediaPlayer.isPlaying();
  }

  /**
   * Gets the duration of the file.
   *
   * @return The duration in milliseconds
   */
  @Override
  public int duration() {
    if (!mIsInitialized) {
      return -1;
    }
    try {
      return mCurrentMediaPlayer.getDuration();
    } catch (IllegalStateException e) {
      return -1;
    }
  }

  /**
   * Gets the current playback position.
   *
   * @return The current position in milliseconds
   */
  @Override
  public int position() {
    if (!mIsInitialized) {
      return -1;
    }
    try {
      return mCurrentMediaPlayer.getCurrentPosition();
    } catch (IllegalStateException e) {
      return -1;
    }
  }

  /**
   * Gets the current playback position.
   *
   * @param whereto The offset in milliseconds from the start to seek to
   * @return The offset in milliseconds from the start to seek to
   */
  @Override
  public int seek(final int whereto) {
    try {
      mCurrentMediaPlayer.seekTo(whereto);
      return whereto;
    } catch (IllegalStateException e) {
      return -1;
    }
  }

  @Override
  public boolean setVolume(final float vol) {
    try {
      mCurrentMediaPlayer.setVolume(vol, vol);
      return true;
    } catch (IllegalStateException e) {
      return false;
    }
  }

  /**
   * Sets the audio session ID.
   *
   * @param sessionId The audio session ID
   */
  @Override
  public boolean setAudioSessionId(final int sessionId) {
    try {
      mCurrentMediaPlayer.setAudioSessionId(sessionId);
      return true;
    } catch (@NonNull IllegalArgumentException | IllegalStateException e) {
      return false;
    }
  }

  /**
   * Returns the audio session ID.
   *
   * @return The current audio session ID.
   */
  @Override
  public int getAudioSessionId() {
    return mCurrentMediaPlayer.getAudioSessionId();
  }

  /** {@inheritDoc} */
  @Override
  public boolean onError(final MediaPlayer mp, final int what, final int extra) {
    mIsInitialized = false;
    mCurrentMediaPlayer.release();
    mCurrentMediaPlayer = new MediaPlayer();
    mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
    if (context != null) {
      Toast.makeText(
              context,
              context.getResources().getString(R.string.unplayable_file),
              Toast.LENGTH_SHORT)
          .show();
    }
    return false;
  }

  /** {@inheritDoc} */
  @Override
  public void onCompletion(final MediaPlayer mp) {
    if (mp.equals(mCurrentMediaPlayer) && mNextMediaPlayer != null) {
      mIsInitialized = false;
      mCurrentMediaPlayer.release();
      mCurrentMediaPlayer = mNextMediaPlayer;
      mIsInitialized = true;
      mNextMediaPlayer = null;
      if (callbacks != null) callbacks.onTrackWentToNext();
    } else {
      if (callbacks != null) callbacks.onTrackEnded();
    }
  }
}
